/**
 * 
 * @author Ákos Szabó - Axi - aaszabo@gmail.com
 * Dec 9, 2011
 *
 */
package com.axi.ga;

import java.util.Enumeration;
import java.util.Vector;



/**
 * This is the main class of the GA (Genethic Algorithm) package
 * It runs a GA according to the given parameters
 */
public class GaEngine extends Thread
{

	public final static int GA_RUN_TILL_TERMINATION = 1;
	public final static int GA_RUN_TILL_FORCED_END = 2;

	/** IGaListener event types */
	private final static int GA_LIFE_STARTED = 1;
	private final static int GA_CYCLE_END = 2;
	private final static int GA_MEET_THE_REQUIREMENT = 4;
	private final static int GA_TERMINATED = 8;
	private final static int GA_LIFE_STOPPED = 16;
	
	
	/** This is the DNS handler */
	IDnsHandler _dnsHandler;
	
	/** The main thread that runs life */
	Thread _life;
	
	Vector _population;
	
	int _populationSize;
	
	/** Tells how to control the end of the life */
	int _runningMode = GA_RUN_TILL_TERMINATION;
	
	/** This stores the current best at the end of every life cycle. */ 
	IDns _theBest;
	
	/** controls the termination of the life thread  */
	boolean _kill = true;
	
	boolean _isLifeRunning = false;
	
	long _cycleCount = 0;
	
	Vector _gaListenerVector = new Vector();
	
	
	/** any bigger is better */
	int _thresholdFitness;
	
	boolean _isContinue = false;
	
	private double _rateOfMutation;
	
	
	
	/**
	 * 
	 * @param argDnsHandler
	 * @param rateOfMutation
	 * @param argTerminationFitness
	 * @param argPopulationSize
	 */
	public GaEngine(IDnsHandler argDnsHandler,
			double rateOfMutation,
			int argTerminationFitness,
			int argPopulationSize,
			IGaListener argListener,
			int argRunningMode)
	{
		this._dnsHandler = argDnsHandler;
		this._rateOfMutation = rateOfMutation;
		this._thresholdFitness = argTerminationFitness;
		this._populationSize = argPopulationSize;
		addGaEngineListener(argListener);
		this._runningMode = argRunningMode;
		
	}
	
	public GaEngine(IDnsHandler argDnsHandler,
			double rateOfMutation,
			int argTerminationFitness,
			int argPopulationSize,
			IGaListener argListener)
	{
		this(argDnsHandler,
				rateOfMutation,
				argTerminationFitness,
				argPopulationSize,
				argListener,
				GA_RUN_TILL_FORCED_END);//GA_RUN_TILL_TERMINATION
		
	}	
	
	public void setTerminationFitnesThreshold(int argThreshold)
	{
		this._thresholdFitness = argThreshold;
	}
	
	public void addGaEngineListener( IGaListener argListener)
	{
		_gaListenerVector.add(argListener);
	}
	
	public void removeGaEngineListener(IGaListener argListener)
	{
		_gaListenerVector.remove(argListener);
	}
	
	private void fireEvent( int argEventId)
	{
	//	_gaListener.lifeStarted();
		Enumeration en = _gaListenerVector.elements();
		
		while( en.hasMoreElements())
		{
			IGaListener lis = (IGaListener)en.nextElement();
			switch(argEventId)
			{
				case GA_LIFE_STARTED : lis.lifeStarted(); break;
				case GA_CYCLE_END : lis.cycleEnd(_theBest); break;
				case GA_MEET_THE_REQUIREMENT : lis.meetTheRequirement(_theBest); break;
				case GA_TERMINATED : lis.terminated(_theBest); break;
				case GA_LIFE_STOPPED : lis.lifeStopped(_theBest); break;
				default : System.out.println("GaEngine > fireEvent > Unknown event type"); 
			}
		}
	}

	
	public void startLife()
	{
		if(!_isLifeRunning)
		{
			_kill = false;
			_life = new Thread(this, "GA");
			_life.start();
			fireEvent(GA_LIFE_STARTED);
			System.out.println("GaEngine : Life started");
		}
		else
		{
			System.out.println("GaEngine : Cannot start life, because it is running already!");
		}
				
	}	
	
	public void stopLife()
	{
		_kill = true;		
		System.out.println("GaEngine : Life stopped");
	}
	
	
	public void continueLife()
	{
		if( _kill && !_isLifeRunning )
		{
			_isContinue = true;
			_kill = false;
			startLife();
			System.out.println("GaEngine : Life continued");
		}
	}
	
	
	
	public IDns getTheBest()
	{
		return _theBest;
	}
	
	public IDnsHandler getTheDnsHandler()
	{
		return _dnsHandler;
	}
	
	public void run()
	{
		if(Thread.currentThread().getName().equals("GA"))
		{
			System.out.println("GaEngine : thread started");
			
			_isLifeRunning = true;
			boolean terminated = false;
			
			
			//ha nem folytatás, akkor originate life
			if(!_isContinue)
			{
				//create population 
				_dnsHandler.originatePopulation(_populationSize);
				
				//get the best
				_theBest = _dnsHandler.getTheBest();	
								
				_cycleCount = 0;				
			}			
			_isContinue = false;
			
			
			while( !_kill )
			{
				System.out.println("GaEngine>thread>while> crossover population" );
				
				_theBest = _dnsHandler.crossOverPopulation();
				//fire the cycle end event
				fireEvent(this.GA_CYCLE_END);
				
				
				System.out.println("GaEngine>thread>while> crossover population done, has the best");
				
				//check if the best meets the requirement
				int fitness = _theBest.getFitness();
				
				if(fitness > _thresholdFitness)
				{
					if(_runningMode == GA_RUN_TILL_TERMINATION)
					{
						terminated = true;
						_kill = true;
					}
					else
					{
						fireEvent(GA_MEET_THE_REQUIREMENT);
						//_gaListener.meetTheRequirement(_theBest);
					}
				}

				if(!_kill)
				{
					fireEvent(GA_CYCLE_END);
					_cycleCount++;				
				}
			}//end of while !_kill
			
			_isLifeRunning = false;

			if(terminated)
			{
				fireEvent(GA_TERMINATED);
				//_gaListener.terminated(_theBest);
			}
			else
			{
				fireEvent(GA_LIFE_STOPPED);
				//_gaListener.lifeStopped(_theBest);
			}
			
			System.out.println("GaEngine : thread terminated");
		}//end of GA thread
	}
	
		
}//end of class GA
